package topology;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.w3c.dom.Document;

import util.XMLUtils;

public class TopologyGenerator {
	
	public static final int SEED = 42;
	public static final double PROB_DENSE_DENSE = 312.0 / (20 * 20);
	public static final double PROB_TRANSIT_TRANSIT = 0.1;
	public static final double PROB_OUTER_OUTER = 0.05;
	public static final double PROB_TRANSIT_DENSE = 183.0 / (20 * 129);
	public static final double PROB_OUTER_DENSE = 29.0 / (900 * 20);
	public static final double PROB_OUTER_TRANSIT = 145.0 / (900 * 129);
	public static final int NUM_PROTOCOL = 5;
	
	private Random rng = new Random(SEED);
	
	private List<GraphNode> denseCore = new LinkedList<GraphNode>();
	private List<GraphNode> transitCore = new LinkedList<GraphNode>();
	private List<GraphNode> outerCore = new LinkedList<GraphNode>();
	private List<List<GraphNode>> allCores = new LinkedList<List<GraphNode>>();
	private Map<Integer, GraphNode> connectedNodes = new HashMap<Integer, GraphNode>();
	
	
	private int denseCoreSize;
	private int transitCoreSize;
	private int outerCoreSize;
	
	public TopologyGenerator(int denseCoreSize, int transitCoreSize, int outerCoreSize){
		this.denseCoreSize = denseCoreSize;
		this.transitCoreSize = transitCoreSize;
		this.outerCoreSize = outerCoreSize;
		allCores.add(denseCore);
		allCores.add(transitCore);
		allCores.add(outerCore);
		populateGraph();
		addProtocol();
	}
	
	private void addProtocol() {
		GraphNode[] pps = getProtocolPeers();
		for(int i = 0; i<pps.length; i++){
			pps[i].setAPIClass(bgpemulation.TrustedAS.class.getName());
			for(int j = i + 1; j<pps.length; j++){
				pps[i].protocolPeer(pps[j]);
			}
		}
	}

	private GraphNode[] getProtocolPeers() {
		GraphNode[] protocolPeers = new GraphNode[NUM_PROTOCOL];
		for(int i = 0; i<NUM_PROTOCOL; i++){
			protocolPeers[i] = getRandomPeer();
		}
		return protocolPeers;
	}

	private GraphNode getRandomPeer() {
		List<GraphNode> core = allCores.get(rng.nextInt(allCores.size()));
		return core.get(rng.nextInt(core.size()));
	}

	private void populateGraph(){
		populateDenseCore();
		populateTransitCore();
		populateOuterCore();
	}
	
	private void populateDenseCore(){
		GraphNode root = new GraphNode(true);
		denseCore.add(root);
		connectedNodes.put(root.getPort(), root);
		initializeRegion(denseCore, denseCoreSize-1, PROB_DENSE_DENSE);
		verifyConnectivity(denseCore);
	}


	private void verifyConnectivity(List<GraphNode> segment) {
		for(GraphNode node : segment){
			if(!node.isConnected()){
				node.randomConnect();
			}
		}
	}

	private void populateTransitCore() {
		initializeRegion(transitCore, transitCoreSize, PROB_TRANSIT_TRANSIT);
		connectRegions(transitCore, denseCore, PROB_TRANSIT_DENSE);
		verifyConnectivity(transitCore);
	}
	

	private void populateOuterCore(){
		initializeRegion(outerCore, outerCoreSize, PROB_OUTER_OUTER);
		connectRegions(outerCore, transitCore, PROB_OUTER_TRANSIT);
		connectRegions(outerCore, denseCore, PROB_OUTER_DENSE);
		verifyConnectivity(outerCore);
	}
	
	private int initializeRegion(List<GraphNode> region, int size, double prob) {
		for(int i = 0; i<size; i++){
			region.add(new GraphNode());
		}
		return connectRegions(region, region, prob);
	}

	private int connectRegions(List<GraphNode> source, List<GraphNode> dest, double prob) {
		int i = 0;
		for(GraphNode node : source){
			for(GraphNode potentialNeighbor : dest){
				if(shouldPeer(node, potentialNeighbor, source.size(), prob)){
					node.peer(potentialNeighbor);
					i+=2;
				}
			}
		}
		return i;
	}
	
	private boolean shouldPeer(GraphNode subject, GraphNode potentialNeighbor,
			int size, double prob) {
		if(subject.peersWith(potentialNeighbor)){
			return false;
		}
		int cutoff = (int)Math.round(prob * size);
		return (rng.nextInt(size) < cutoff);
	}
	
	public static void main(String[] args) throws IOException{
		String in = args[0];
		String out = args[1];
		int denseCoreSize = Integer.parseInt(args[2]);
		int transitCoreSize = Integer.parseInt(args[3]);
		int outerCoreSize = Integer.parseInt(args[4]);
		TopologyGenerator tg = new TopologyGenerator(denseCoreSize, transitCoreSize, outerCoreSize);
		tg.writeOutput(in, out);
		printStats(args[5]);
	}
	
	public static void printStats(String graphFile) throws IOException{
		System.out.println("Connected Graph Size: " + GraphNode.connectedNodes.size());
		BufferedWriter bw = new BufferedWriter(new FileWriter(new File(graphFile)));
		writeGraph(bw);
	}

	private static void writeGraph(BufferedWriter bw) throws IOException {
		bw.write("graph G\n{\n");
		for(GraphNode node : GraphNode.connectedNodesOrder){
			node.writeNode(bw);
		}
		for(GraphNode node : GraphNode.connectedNodesOrder){
			node.writePeers(bw);
		}
		bw.write("\n}\n");
		bw.close();
	}

	private void writeOutput(String in, String out) throws IOException{
		Document base = XMLUtils.getDocument(in);
		appendToDoc(base);
		XMLUtils.saveDocument(base, out);
	}
	
	private void appendToDoc(Document base) {
		for(List<GraphNode> core : allCores){
			for(GraphNode node : core){
				node.serialize(base);
			}
		}
	}
}
